package ualrcdg.server;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList;

import javax.xml.transform.TransformerException;

import org.w3c.dom.Document;
import org.xml.sax.SAXException;

import ualrcdg.shared.CDGXMLHelper;
import ualrcdg.shared.XMLHelper;

/**
 * Utility class responsible for handling all direct interaction with CDG Clients.
 * Listens for CDG Request objects, submits them to the CDGServer, and forwards the Response objects back to the CDG Client.  
 */
public class Communicator implements Runnable
{
	private final Socket socket;
	private final DataInputStream dis;
	private final DataOutputStream dos;
	
	/**
	 * Primary constructor
	 * 
	 * @param socket the {@link Socket} that this Communicator instance will use for receiving requests and sending responses
	 * @throws IOException If the Communicator is unable to get the input or output streams associated with the socket 
	 */
	public Communicator( Socket socket ) throws IOException
	{
		this.socket = socket;
		try
		{
			dis = new DataInputStream( socket.getInputStream( ) );
		}
		catch( IOException e )
		{
			throw new IOException( "Error in Communicator: unable to fetch input stream.", e );
		}
		try
		{
			dos = new DataOutputStream( socket.getOutputStream( ) );
		}
		catch( IOException e )
		{
			throw new IOException( "Error in Communicator: unable to fetch output stream.", e );
		}
		
		System.out.println( "New Communicator started with socket = " + socket.toString( ) );
	}

	/**
	 * @see java.lang.Runnable#run()
	 */
	@Override
	public void run( )
	{
		while( !socket.isClosed( ) && socket.isConnected( ) )
		{
			Document request = receiveFile( );
			
			if( request == null )
			{
				closeSocket( );
				return;
			}
			
			ArrayList< Document > response = CDGServer.getInstance( ).serve( request );
	
			for( Document doc : response )
			{
				if( !sendFile( doc ) )
				{
					closeSocket( );
					return;
				}
			}
		}
	}
	
	/**
	 * Convenience method for receiving a request file from this Communicator's input stream.
	 * 
	 * @return an XML document request from a CDG Client application instance
	 */
	private Document receiveFile( )
	{
		try
		{
			System.out.println( "Communicator: trying to receiveFile" );
			if( socket.isClosed( ) || !socket.isConnected( ) )
			{
				throw new IOException( "Error in Communicator: socket closed before request document could be read." );
			}
			
			int bytesRemaining = dis.readInt( );
			if( bytesRemaining == 0 )
			{
				throw new IOException( "Error in Communicator: end-of-stream read." );
			}
			byte[] message = new byte[bytesRemaining];
			dis.read( message );
			
			return XMLHelper.parseDocumentFromBytes( message );
		}
		catch(  SAXException | IOException | TransformerException e )
		{
			System.out.println( "Error in Communicator: unable to parse request document from stream." );
			e.printStackTrace( );
			return null;
		}
	}
	
	/**
	 * Convenience method for sending a file to this Communicator's output stream.
	 * 
	 * @param file the XML response file to send to the CDG Client associated with this Communicator. 
	 * @return true if the file was successfully sent, false otherwise
	 */
	private boolean sendFile( Document file )
	{
		try
		{
			byte[] message = CDGXMLHelper.writeDocumentToBytes( file );
			dos.writeInt( message.length );
			dos.write( message );
			dos.flush( );
			return true;
		}
		catch( IOException |TransformerException e )
		{
			System.out.println( "Error in Communicator: unable to write response document to stream." );
			e.printStackTrace( );
			return false;
		}		
	}
	
	/**
	 * Closes this Communicator's socket and logs any exceptions that occur.  
	 */
	public void closeSocket( )
	{
		try
		{
			socket.shutdownInput( );
			socket.shutdownOutput( );
			socket.close( );
		}
		catch( IOException e )
		{
			System.out.println( "Error in Communicator: unable to close socket ( " + socket.toString( ) + " )" );
		}
	}
}
